﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
//using BaMusaSoft.NumToWords;
using BaMusaSoft.RPS.DataModel;
using Microsoft.Office.Interop.Word;
using Task = System.Threading.Tasks.Task;
using WordApplication = Microsoft.Office.Interop.Word.Application;
using ThreadingTasks = System.Threading.Tasks;

namespace BaMusaSoft.RPS.RPSServices
{
    public class SchedulePrinterService : IJobPrinter<Schedule>
    {
        #region "Fields"

        private static readonly object Locker = new object();
        private static bool _isBusy;
        private static SchedulePrinterService _instace;

        private static WordApplication _wordApp;
        private readonly Queue<Schedule> _jobs;
        private readonly string _wordTemplatePath;
        private bool _serviceStarted;
        private Document _wordDocument;
        private Documents _wordDocuments;

        #endregion

        #region "Construction"

        private SchedulePrinterService(string wordTemplatePath)
        {
            _jobs = new Queue<Schedule>();
            _wordTemplatePath = wordTemplatePath;
            _serviceStarted = false;
        }

        public static SchedulePrinterService Create(string wordTemplatePath)
        {
            lock (Locker)
            {
                return _instace ?? (_instace = new SchedulePrinterService(wordTemplatePath));
            }
        }

        #endregion

        #region "ISerives implementation"

        public void Start()
        {
            //When start service, start consume method and prevent another start untel stop method called.
            lock (Locker)
            {
                if (!_serviceStarted)
                {
                    Task.Factory.StartNew(Consume);
                    _serviceStarted = true;
                }
            }
            _wordApp = CreateWord();
        }

        public void Stop()
        {
            lock (Locker)
            {
                _serviceStarted = false;
                _jobs.Clear();
                _jobs.Enqueue(null);
                Monitor.Pulse(Locker);
                ReleaseComCompnenets(true);
            }
        }


        public void AddJob(Schedule job)
        {
            if (job == null)
                throw new ArgumentNullException("job");
                    //Don't allow null customer as this is indication for stoping service.
            Task.Factory.StartNew(() =>
                                      {
                                          lock (Locker)
                                          {
                                              _jobs.Enqueue(job);
                                              Monitor.Pulse(Locker);
                                          }
                                      });
            OnJobStateChanged(job, ServiceJobState.Added);
        }

        public bool Isbusy
        {
            //IsBusy maintaint it's state by one of these choices.
            //1- Jobs queue has jobs. =>>>>>> IsBusy == true.
            //2- isBusy flag == true. =>>>>>> Isbusy == true.
            //3- Jobs queue has jobs AND isBusy flag == true =>>>>>>>>>> Isbusy == true.
            //4- Jobs queue doesn't have jobs AND isbusy flag == false =>>IsBusy == False.
            //From cases above, we can see that this property(IsBusy) will only be false
            //If and only if Jobs.Count == 0 AND isBusy flage == false.
            get
            {
                lock (Locker)
                {
                    if (_jobs.Count == 0 && _isBusy == false)
                    {
                        return false;
                    }
                    return true;
                }
            }
        }

        public event EventHandler<ServiceCompletedEventArgs> JobCompleted;
        public event EventHandler<ServiceJobChangedEventArgs<Schedule>> JobStateChanged;

        #endregion

        #region "Preducser Consumer"

        /// <summary>
        /// Print schedule in word template.
        /// </summary>
        /// <exception cref="NotSupportedException"></exception>
        /// <exception cref="NullReferenceException"></exception>
        /// <exception cref="FormatException"></exception>
        /// <exception cref="COMException"></exception>
        private void Consume()
        {
            Console.WriteLine("Service Started......");
            while (true) //Keep consuming until told otherwise
            {
                Console.WriteLine("Still spin");
                Schedule job;
                lock (Locker)
                {
                    while (_jobs.Count == 0) Monitor.Wait(Locker);
                    job = _jobs.Dequeue();
                }
                if (job == null) // This signals out exit.
                {
                    Console.WriteLine("Good by....");
                    //isBusy = false; //Not here. isbusy maintained by StartPrintinInWord.
                    return;
                }
                StartPrintingInWord(job); //Do the job;
            }
        }

        private void StartPrintingInWord(Schedule job)
        {
            try
            {
                _isBusy = true;
                Print(job);
                OnJobCompleted(CompletionResult.JobDone, null, null);
                OnJobStateChanged(job, ServiceJobState.Printed);
            }
            catch (Exception ex)
            {
                OnJobCompleted(CompletionResult.Exception, ex, null);
                OnJobStateChanged(job, ServiceJobState.Canceled);
            }
            finally
            {
                //Set isBusy flag to 'False' even in case of failure; as in all cases Im not doing any more Ms-Word printing .
                //See IsBusy Property for more about isBusy docs.
                _isBusy = false;
            }
        }

        #endregion

        #region "Helper"

        private void OnJobCompleted(CompletionResult result, Exception exception, object data)
        {
            if (JobCompleted != null)
            {
                JobCompleted(this, new ServiceCompletedEventArgs(result, exception, data));
            }
        }

        private void OnJobStateChanged(Schedule job, ServiceJobState jobState)
        {
            if (JobStateChanged != null)
            {
                JobStateChanged(this, new ServiceJobChangedEventArgs<Schedule>(job, jobState));
            }
        }

        private void ReleaseComCompnenets(bool isServiceClosing)
        {
            if (isServiceClosing)
            {
                if (_wordApp != null)
                {
                    _wordApp.Quit();
                    Marshal.FinalReleaseComObject(_wordApp);
                    _wordApp = null;
                }
            }
            if (_wordDocuments != null)
            {
                Marshal.FinalReleaseComObject(_wordDocuments);
                _wordDocuments = null;
            }
            if (_wordDocument != null)
            {
                _wordDocument.Close(WdSaveOptions.wdDoNotSaveChanges);
                Marshal.FinalReleaseComObject(_wordDocument);
                _wordDocument = null;
            }
        }

        #endregion

        #region ""Microsoft Word

        private static WordApplication CreateWord()
        {
            if (_wordApp == null)
            {
                return new WordApplication();
            }
            return _wordApp;
        }

        private void Print(Schedule job)
        {
            try
            {
                _wordDocuments = _wordApp.Documents;
                _wordDocument = _wordDocuments.Add(_wordTemplatePath);
                foreach (Field field in _wordDocument.Fields)
                {
                    //Thread.Sleep(10000);
                    PrintField(field, job);
                }
                _wordDocument.PrintOut();
            }
            finally
            {
                ReleaseComCompnenets(false);
            }
        }

        private void PrintField(Field f, Schedule schedule)
        {
            if (f.Code.Text.Contains("ScheduleId"))
            {
                f.Result.Text = schedule.ScheduleId;
                return;
            }
            if (f.Code.Text.Contains("RegistredDay"))
            {
                f.Result.Text = (Helper.ParseDateString(schedule.RegistredAt)).ToString("dddd", new CultureInfo("ar-SA"));
                return;
            }
            if (f.Code.Text.Contains("RegistredAt"))
            {
                string regDate = Helper.DateMiror(schedule.RegistredAt);
                f.Result.Text = regDate;
                return;
            }
            if (f.Code.Text.Contains("CustomerName"))
            {
                f.Result.Text = schedule.Contract.Customer.Name;
                f.Result.Font.Size = 14.0f;
                f.Result.Font.Bold = 1;
                return;
            }
            if (f.Code.Text.Contains("ContractNo"))
            {
                f.Result.Text = schedule.Contract.ContractNo.ToString();
                return;
            }
            if (f.Code.Text.Contains("PropertyDescription"))
            {
                f.Result.Text = schedule.Contract.Property.Description;
                return;
            }
            if (f.Code.Text.Contains("Rent"))
            {
                f.Result.Text = schedule.Contract.RentBalance.ToString();
                return;
            }
            if (f.Code.Text.Contains("Deposit"))
            {
                f.Result.Text = schedule.Contract.DepositBalance.ToString();
                return;
            }
            if (f.Code.Text.Contains("Maint"))
            {
                f.Result.Text = schedule.Contract.MaintenanceBalance.ToString();
                return;
            }
            if (f.Code.Text.Contains("Total"))
            {
                f.Result.Text = schedule.Contract.Total.ToString();
                return;
            }
            //************** AmountDue **************//
            if (f.Code.Text.Contains("AmountDue01"))
            {
                if (schedule.ScheduleDetails.Count > 0)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(0).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue02"))
            {
                if (schedule.ScheduleDetails.Count > 1)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(1).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue03"))
            {
                if (schedule.ScheduleDetails.Count > 2)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(2).AmountDue.ToString();
                }

                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue04"))
            {
                if (schedule.ScheduleDetails.Count > 3)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(3).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue05"))
            {
                if (schedule.ScheduleDetails.Count > 4)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(4).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue06"))
            {
                if (schedule.ScheduleDetails.Count > 5)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(5).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue07"))
            {
                if (schedule.ScheduleDetails.Count > 6)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(6).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue08"))
            {
                if (schedule.ScheduleDetails.Count > 7)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(7).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue09"))
            {
                if (schedule.ScheduleDetails.Count > 8)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(8).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue10"))
            {
                if (schedule.ScheduleDetails.Count > 9)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(9).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue11"))
            {
                if (schedule.ScheduleDetails.Count > 10)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(10).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("AmountDue12"))
            {
                if (schedule.ScheduleDetails.Count > 11)
                {
                    f.Result.Text = schedule.ScheduleDetails.ElementAt(11).AmountDue.ToString();
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            //*****************Date Due ************************************//
            if (f.Code.Text.Contains("DateDue01"))
            {
                if (schedule.ScheduleDetails.Count > 0)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(0).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue02"))
            {
                if (schedule.ScheduleDetails.Count > 1)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(1).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue03"))
            {
                if (schedule.ScheduleDetails.Count > 2)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(2).DateDue);
                }

                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue04"))
            {
                if (schedule.ScheduleDetails.Count > 3)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(3).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue05"))
            {
                if (schedule.ScheduleDetails.Count > 4)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(4).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue06"))
            {
                if (schedule.ScheduleDetails.Count > 5)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(5).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue07"))
            {
                if (schedule.ScheduleDetails.Count > 6)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(6).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue08"))
            {
                if (schedule.ScheduleDetails.Count > 7)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(7).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue09"))
            {
                if (schedule.ScheduleDetails.Count > 8)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(8).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue10"))
            {
                if (schedule.ScheduleDetails.Count > 9)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(9).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue11"))
            {
                if (schedule.ScheduleDetails.Count > 10)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(10).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }
            if (f.Code.Text.Contains("DateDue12"))
            {
                if (schedule.ScheduleDetails.Count > 11)
                {
                    f.Result.Text = Helper.DateMiror(schedule.ScheduleDetails.ElementAt(11).DateDue);
                }
                else
                {
                    f.Result.Text = null;
                }
                return;
            }

            if (f.Code.Text.Contains("SumOfPayments"))
            {
                int total = SumScheduleDetailsAmountDue(schedule);
                f.Result.Text = total.ToString();
                return;
            }
            if (f.Code.Text.Contains("SayInWords"))
            {
                int total = SumScheduleDetailsAmountDue(schedule);
                f.Result.Text = "";//SayNumber.ToWords(total);
                return;
            }
            if (f.Code.Text.Contains("SignerName"))
            {
                f.Result.Text = schedule.Signer.Name;
                return;
            }
            if (f.Code.Text.Contains("SignerId"))
            {
                f.Result.Text = schedule.Signer.IdNumber;
                return;
            }
            if (f.Code.Text.Contains("IdDate"))
            {
                f.Result.Text = Helper.DateMiror(schedule.Signer.IdDate);
                return;
            }
            if (f.Code.Text.Contains("Mobile"))
            {
                f.Result.Text = schedule.Signer.Mobile;
                return;
            }
            if (f.Code.Text.Contains("FirstPhone"))
            {
                f.Result.Text = schedule.Signer.FirstPhone;
                return;
            }
        }


        private static int SumScheduleDetailsAmountDue(Schedule schedule)
        {
            int n = (from p in schedule.ScheduleDetails
                     select p.AmountDue).Sum();
            return n;
        }

        #endregion
    }
}